#include <tinx/buffer.h>
#include <tinx/task.h>
#include <tinx/device.h>
#include <tinx/fs.h>
#include <tinx/user.h>
#include <tinx/memory.h>
#include <tinx/time.h>
#include <tinx/string.h>
#include <tinx/stdlib.h>
#include <tinx/assert.h>

#include "minix.h"

idx_t minix_ialloc(super_t *super)
{
    minix_desc_t *desc = (minix_desc_t *)super->desc;
    minix_data_t *data = (minix_data_t *)super->data;

    buffer_t *buf = NULL;
    bitmap_t map;

    idx_t bit = EOF;

    for (size_t i = 0; i < desc->imap_blocks; i++)
    {
        buf = data->imaps[i];

        bitmap_make(&map, buf->data, BLOCK_SIZE, i * BLOCK_BITS);

        // 分配一个位
        bit = bitmap_scan(&map, 1);

        if (bit != EOF)
        {
            // 将改动写入硬盘
            buf->dirty = true;;
            bwrite(buf);
            break;
        }
    }

    return bit;
}

void minix_ifree(super_t *super, idx_t nr)
{
    minix_desc_t *desc = (minix_desc_t *)super->desc;
    minix_data_t *data = (minix_data_t *)super->data;

    buffer_t *buf = NULL;
    bitmap_t map;

    idx_t bit = EOF;

    for (size_t i = 0; i < desc->imap_blocks; i++)
    {
        if (nr > (i + 1) * BLOCK_BITS)
        {
            continue;
        }

        buf = data->imaps[i];

        bitmap_make(&map, buf->data, BLOCK_SIZE, i * BLOCK_BITS + desc->data - 1);
        bitmap_set(&map, nr, false);
        buf->dirty = true;;
        bwrite(buf);
        break;
    }
}

idx_t minix_balloc(super_t *super)
{
    minix_desc_t *desc = (minix_desc_t *)super->desc;
    minix_data_t *data = (minix_data_t *)super->data;

    buffer_t *buf = NULL;
    bitmap_t map;

    idx_t bit = EOF;

    for (size_t i = 0; i < desc->zmap_blocks; i++)
    {
        buf = data->zmaps[i];
        bitmap_make(&map, buf->data, BLOCK_SIZE, i * BLOCK_BITS + desc->data - 1);

        // 分配一个位
        bit = bitmap_scan(&map, 1);

        if (bit != EOF)
        {
            // 将改动写入硬盘
            buf->dirty = true;;
            bwrite(buf);
            break;
        }
    }

    return bit;
}

void minix_bfree(super_t *super, idx_t nr)
{
    minix_desc_t *desc = (minix_desc_t *)super->desc;
    minix_data_t *data = (minix_data_t *)super->data;

    buffer_t *buf = NULL;
    bitmap_t map;

    idx_t bit = EOF;

    for (size_t i = 0; i < desc->zmap_blocks; i++)
    {
        if (nr > (i + 1) * BLOCK_BITS)
        {
            continue;
        }

        buf = data->zmaps[i];

        bitmap_make(&map, buf->data, BLOCK_SIZE, i * BLOCK_BITS);
        bitmap_set(&map, nr, false);
        // 将改动写入硬盘
        buf->dirty = true;;
        bwrite(buf);
        break;
    }
}

idx_t minix_bmap(inode_t *inode, idx_t block, bool creat)
{
    minix_inode_t *minode = (minix_inode_t *)inode->desc;
    u16 *array = minode->zone;
    u16 index = block;

    buffer_t *buf = NULL;
    int level = 0;
    int divider = 1;

    if (block < DIRECT_BLOCK)
    {
        goto reckon;
    }

    block -= DIRECT_BLOCK;
    if (block < INDIRECT1_BLOCK)
    {
        divider = 1;
        level = 1;
        index = DIRECT_BLOCK;
        goto reckon;
    }

    level = 2;
    index = DIRECT_BLOCK + 1;
    divider = BLOCK_INDEXES;
    block -= INDIRECT1_BLOCK;

reckon:
    // 如果没有块并且 creat 置位就分配一个块
    if (!array[index] && creat)
    {
        array[index] = minix_balloc(inode->super);
    }

    // 释放之前的缓冲
    brelse(buf);

    // 层级结束就返回
    if (!level || !array[index])
        return array[index];

    // 获得下一块的缓冲
    buf = bread(inode->dev, array[index], BLOCK_SIZE);

    // 获取下一块的内容
    array = (u16 *)buf->data;
    index = block / divider;
    block %= divider;
    divider /= BLOCK_INDEXES;
    level--;
    goto reckon;
}

void minix_inode_bfree(inode_t *inode, u16 *array, u16 index, int level)
{
    if (!array[index])
        return;

    if (!level)
    {
        minix_bfree(inode->super, array[index]);
        array[index] = 0;
        return;
    }

    buffer_t *buf = bread(inode->dev, array[index], BLOCK_SIZE);
    for (size_t i = 0; i < BLOCK_INDEXES; i++)
    {
        minix_inode_bfree(inode, (u16 *)buf->data, i, level - 1);
    }
    brelse(buf);
    minix_bfree(inode->super, array[index]);
    array[index] = 0;
    minix_inode_t *minode = (minix_inode_t *)inode->desc;
}

int minix_truncate(inode_t *inode)
{
    assert(ISDIR(inode->mode) || ISFILE(inode->mode));

    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    u16 *array = minode->zone;
    for (size_t i = 0; i < DIRECT_BLOCK; i++)
    {
        minix_inode_bfree(inode, array, i, 0);
    }

    minix_inode_bfree(inode, array, DIRECT_BLOCK, 1);
    minix_inode_bfree(inode, array, DIRECT_BLOCK + 1, 2);

    minode->size = inode->size = 0;
    minode->mtime = ktime();
    inode->mtime = minode->mtime;
    inode->atime = inode->mtime;

    inode->buf->dirty = true;;
    bwrite(inode->buf);
    return EOK;
}

int minix_read(inode_t *inode, void *addr, int count, int offset)
{
    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    if (offset >= minode->size)
    {
        return -EEOF;
    }

    count = MIN(count, minode->size - offset);
    int left = count;

    while (left)
    {
        idx_t block = minix_bmap(inode, offset / BLOCK_SIZE, false);
        buffer_t *buf = bread(inode->dev, block, BLOCK_SIZE);

        int start = offset % BLOCK_SIZE;
        int chars = MIN(BLOCK_SIZE - start, left);

        u8 *src = (u8 *)buf->data + start;
        memcpy(addr, src, chars);

        offset += chars;
        addr += chars;
        left -= chars;
        brelse(buf);
    }

    inode->atime = ktime();

    return count;
}

int minix_write(inode_t *inode, void *addr, int count, int offset)
{
    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    int stop = offset + count;
    if (stop > minode->size)
    {
        minode->size = inode->size = stop;
    }

    int left = count;

    while (left)
    {
        idx_t block = minix_bmap(inode, offset / BLOCK_SIZE, true);
        buffer_t *buf = bread(inode->dev, block, BLOCK_SIZE);

        int start = offset % BLOCK_SIZE;
        int chars =  MIN(BLOCK_SIZE - start, left);

        u8 *src = (u8 *)buf->data + start;
        memcpy(src, addr, chars);
        buf->dirty = true;;

        offset += chars;
        addr += chars;
        left -= chars;
        brelse(buf);
    }

    inode->buf->dirty = true;;
    bwrite(inode->buf);

    minode->mtime = inode->atime = inode->mtime = ktime();

    return count;
}

int minix_readdir(file_t *file, dentry_t *entry, int count)
{
    inode_t *inode = file->inode;
    buffer_t *buf = NULL;
    minix_dentry_t *ptr = NULL;

    int len = 0;

    for (; file->offset < inode->size; ptr++, file->offset += sizeof(minix_dentry_t))
    {
        if (!buf || (u32)ptr >= (u32)buf->data + BLOCK_SIZE)
        {
            brelse(buf);
            buf = bread(inode->dev, minix_bmap(inode, file->offset / BLOCK_SIZE, false), BLOCK_SIZE);
            ptr = (minix_dentry_t *)((u32)buf->data + (file->offset % BLOCK_SIZE));
        }
        if (!ptr->nr)
        {
            continue;
        }

        u32 namelen = strnlen(ptr->name, MINIX_NAME_LEN);
        u32 size = sizeof(dentry_t) + namelen;

        if (count - len <= size && count)
        {
            break;
        }

        // 填充 entry
        memset(entry, 0, size);
        entry->nr = ptr->nr;
        entry->length = size;
        entry->namelen = namelen;
        memcpy(entry->name, ptr->name, namelen);

        entry = (dentry_t *)((u32)entry + size);
        len += size;

        if (!count)
        {
            file->offset += sizeof(minix_dentry_t);
            break;
        }
    }
    brelse(buf);
    return len;
}

static inline idx_t inode_block(super_t *super, idx_t nr)
{
    minix_desc_t *desc = (minix_desc_t *)super->desc;
    idx_t idx = 2 + desc->imap_blocks + desc->zmap_blocks + ((nr - 1) / BLOCK_INODES);
    return idx;
}

inode_t *minix_iget(super_t *super, idx_t nr)
{
    inode_t *inode = find_inode(super, nr);
    if (inode)
    {
        inode->count++;
        inode->atime = ktime();
        return fit_inode(inode);
    }

    inode = get_free_inode();

    idx_t block = inode_block(super, nr);
    idx_t offset = (nr - 1) % BLOCK_INODES;

    list_push(&super->inode_list, &inode->node);
    buffer_t *buf = bread(super->dev, block, BLOCK_SIZE);

    minix_inode_t *minode = &((minix_inode_t *)buf->data)[offset];

    inode->atime = ktime();
    inode->mtime = minode->mtime;
    inode->ctime = minode->mtime;

    inode->uid = minode->uid;
    inode->gid = minode->gid;
    inode->block_size = BLOCK_SIZE;
    inode->nlinks = minode->nlinks;
    inode->mode = minode->mode;
    inode->size = minode->size;
    if (ISCHR(inode->mode) || ISBLK(inode->mode))
        inode->rdev = minode->zone[0];

    inode->dev = super->dev;
    inode->desc = minode;
    inode->buf = buf;
    inode->nr = nr;
    inode->count++;
    inode->super = super;

    inode->op = super->op;
    inode->data = super->data;

    return fit_inode(inode);
}

inode_t *minix_new_inode(super_t *super, idx_t nr)
{
    inode_t *inode = find_inode(super, nr);
    assert(!inode);

    task_t *task = running_task();
    inode = get_free_inode();

    idx_t block = inode_block(super, nr);
    idx_t offset = (nr - 1) % BLOCK_INODES;

    list_push(&super->inode_list, &inode->node);
    buffer_t *buf = bread(super->dev, block, BLOCK_SIZE);

    minix_inode_t *minode = &((minix_inode_t *)buf->data)[offset];
    memset(minode, 0, sizeof(minix_inode_t));

    minode->mode = 0777 & (~task->umask);
    minode->uid = task->uid;

    inode->dev = super->dev;
    inode->atime = ktime();
    inode->mtime = minode->mtime;
    inode->ctime = minode->mtime;

    inode->uid = minode->uid;
    inode->gid = minode->gid;
    inode->block_size = BLOCK_SIZE;
    inode->nlinks = minode->nlinks;
    inode->mode = minode->mode;
    inode->size = minode->size;

    inode->desc = minode;
    inode->buf = buf;
    inode->buf->dirty = true;;
    inode->nr = nr;
    inode->count++;
    inode->super = super;
    inode->op = super->op;
    inode->data = super->data;

    return inode;
}

int minix_open(inode_t *dir, char *name, int flags, int mode, inode_t **result)
{
    inode_t *inode = NULL;
    minix_inode_t *minode = NULL;
    char *next = NULL;
    buffer_t *buf = NULL;
    task_t *task = running_task();

    int ret = EOK;

    if ((flags & O_TRUNC) && (flags & O_ACCMODE) == O_RDONLY)
        flags |= O_RDWR;

    if (!name[0])
    {
        ret = -ENOENT;
        goto rollback;
    }

    minix_dentry_t *entry;
    buf = minix_find_entry(&dir, name, &next, &entry);
    if (buf)
    {
        inode = minix_iget(dir->super, entry->nr);
        goto makeup;
    }

    if (!(flags & O_CREAT))
    {
        ret = -ENOENT;
        goto rollback;
    }

    buf = minix_add_entry(dir, name, &entry);
    entry->nr = minix_ialloc(dir->super);
    inode = minix_new_inode(dir->super, entry->nr);
    minode = (minix_inode_t *)inode->desc;

    minode->nlinks++;
    inode->nlinks = minode->nlinks;

    mode &= 0777 & ~task->umask;
    mode |= IFREG;

    minode->mode = mode;
    inode->mode = mode;

makeup:
    if (!ISDIR(inode->mode) && next)
    {
        ret = -EISDIR;
        goto rollback;
    }

    if (!inode->op->permission(inode, ACC_MODE(flags & O_ACCMODE)))
    {
        ret = -EPERM;
        goto rollback;
    }

    if (!minode)
        minode = (minix_inode_t *)inode->desc;

    if (ISDIR(minode->mode) && ((flags & O_ACCMODE) != O_RDONLY))
    {
        ret = -EISDIR;
        goto rollback;
    }

    inode->atime = ktime();

    if (flags & O_TRUNC)
        minix_truncate(inode);

    *result = inode;

    brelse(buf);
    return ret;

rollback:
    brelse(buf);
    iput(inode);
    return ret;
}

int minix_close(inode_t *inode)
{
    if (inode->buf->dirty)
    {
        bwrite(inode->buf);
    }
    inode->count--;
    if (inode->count)
    {
        minix_inode_t *minode = inode->desc;
        inode->size = minode->size;
        inode->nlinks = minode->nlinks;
        inode->rdev = minode->zone[0];
        return EOK;
    }
    inode->super = NULL;
    inode->mount = NULL;
    inode->nr = 0;
    inode->dev = -1;
    brelse(inode->buf);
    list_remove(&inode->node);
    return EOK;
}

int minix_match(char *name, char *entry_name, char **next)
{
    char *lhs = (char *)name;
    char *rhs = (char *)entry_name;
    // 循环比较
    int count = MINIX_NAME_LEN;
    while (*lhs == *rhs && *lhs != EOS && *rhs != EOS && count)
    {
        lhs++;
        rhs++;
        count--;
    }
    if (!count)
        return true;
    if (*rhs)
        return false;
    // 下一个不是分割符返回 false
    if (*lhs && !IS_SEPARATOR(*lhs))
        return false;
    // 是分隔符就获得下一个节点的名字, 并且返回 true
    if (IS_SEPARATOR(*lhs))
    {
        lhs++;
        *next = lhs;
    }
    return true;
}

// 在 dir 中寻找目录项
buffer_t *minix_find_entry(inode_t **dir, char *name, char **next, minix_dentry_t **entry)
{
    // 如果根目录也要返回的话就说明要换分区了
    if (minix_match(name, "..", next) && (*dir)->nr == 1)
    {
        super_t *super = (*dir)->super;
        inode_t *inode = *dir; // 获得此超级块根目录
        (*dir) = super->imount; // 把目录换成挂载目录
        (*dir)->count++;
        iput(inode);
    }

    minix_inode_t *minode = (minix_inode_t *)(*dir)->desc;

    int entries = minode->size / sizeof(minix_dentry_t);
    buffer_t *buf = NULL;
    minix_dentry_t *ptr = NULL;

    for (size_t i = 0; i < entries; i++, ptr++)
    {
        if (!buf || (u32)ptr >= (u32)buf->data + BLOCK_SIZE)
        {
            brelse(buf);
            buf = bread((*dir)->dev, minix_bmap(*dir, i / BLOCK_DENTRYS, false), BLOCK_SIZE);
            ptr = (minix_dentry_t *)buf->data;
        }
        if (!ptr->nr)
        {
            continue;
        }
        if (minix_match(name, ptr->name, next))
        {
            *entry = ptr;
            return buf;
        }
    }
    brelse(buf);
    return NULL;
}

// 在 dir 中添加一个目录项
buffer_t *minix_add_entry(inode_t *dir, char *name, minix_dentry_t **entry)
{
    buffer_t *buf = NULL;
    minix_dentry_t *ptr = NULL;

    minix_inode_t *minode = (minix_inode_t *)dir->desc;

    for (size_t i = 0; true; i++, ptr++)
    {
        if (!buf || (u32)ptr >= (u32)buf->data + BLOCK_SIZE)
        {
            brelse(buf);
            buf = bread(dir->dev, minix_bmap(dir, i / BLOCK_DENTRYS, true), BLOCK_SIZE);
            ptr = (minix_dentry_t *)buf->data;
        }
        if ((i + 1) * sizeof(minix_dentry_t) > minode->size)
        {
            ptr->nr = 0;
            minode->size += sizeof(minix_dentry_t);
            dir->size = minode->size;
            dir->buf->dirty = true;
        }
        if (ptr->nr)
        {
            continue;
        }
        memcpy(ptr->name, name, strnamelen(name));
        dir->mtime = minode->mtime = ktime();
        buf->dirty = true;;
        *entry = ptr;
        return buf;
    }

rollback:
    brelse(buf);
    return NULL;
}

int minix_permission(inode_t *inode, int mask)
{
    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    u16 mode = minode->mode;

    if (!minode->nlinks)
        return false;

    task_t *task = running_task();
    user_t *user = user_get(task->uid);

    if (user->super)
        return true;

    if (task->uid == minode->uid)
        mode >>= 6;

    if ((mode & (u16)mask & 0b111) == mask)
        return true;
    return false;
}

int minix_namei(inode_t *dir, char *name, char **next, inode_t **result)
{
    assert(name[0]);

    minix_dentry_t *entry;
    buffer_t *buf = minix_find_entry(&dir, name, next, &entry);
    if (!buf)
    {
        return EOF;
    }

    inode_t *inode = minix_iget(dir->super, entry->nr);
    *result = inode;

    brelse(buf);
    return EOK;
}

int minix_mkdir(inode_t *dir, char *name, int mode)
{
    task_t *task = running_task();

    char *next;
    minix_dentry_t *entry;
    buffer_t *ebuf = minix_find_entry(&dir, name, &next, &entry);

    int ret = EOK;

    if (ebuf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    ebuf = minix_add_entry(dir, name, &entry);
    entry->nr = minix_ialloc(dir->super);
    ebuf->dirty = true;;

    inode_t *inode = minix_new_inode(dir->super, entry->nr);

    minix_inode_t *iminode = (minix_inode_t *)inode->desc;
    iminode->size = inode->size = sizeof(minix_dentry_t) * 2;
    iminode->nlinks = 2;
    iminode->mode = IFDIR | (mode & 0777 & ~task->umask);
    iminode->zone[0] = minix_bmap(inode, 0, true);

    minix_inode_t *dminode = (minix_inode_t *)dir->desc;

    dminode->nlinks++;
    dir->buf->dirty = true;;

    buffer_t *buf = bread(dir->super->dev, iminode->zone[0], BLOCK_SIZE);
    entry = (minix_dentry_t *)buf->data;

    entry->nr = inode->nr;
    strcpy(entry->name, ".");
    entry++;

    entry->nr = dir->nr;
    strcpy(entry->name, "..");

    buf->dirty = true;;

    brelse(ebuf);
    brelse(buf);
    iput(inode);
    return ret;

rollback:
    brelse(ebuf);
    return ret;
}

static bool dir_empty(inode_t *inode)
{
    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    int entries = minode->size / sizeof(minix_dentry_t);
    assert(entries >= 2);

    buffer_t *buf = NULL;
    minix_dentry_t *entry = NULL;

    bool ret = true;

    for (size_t i = 0; i < entries; i++, entry++)
    {
        if (!buf || (u32)entry >= (u32)buf->data + BLOCK_SIZE)
        {
            brelse(buf);
            idx_t block = minix_bmap(inode, i / BLOCK_DENTRYS, false);
            buf = bread(inode->dev, block, BLOCK_SIZE);
            entry = (minix_dentry_t *)buf->data;
        }
        if (!entry->nr)
            continue;
        if (strcmp(entry->name, "..") && strcmp(entry->name, "."))
        {
            ret = false;
            goto rollback;
        }
    }

rollback:
    brelse(buf);
    return ret;
}

int minix_rmdir(inode_t *dir, char *name)
{
    char *next = NULL;
    inode_t *inode = NULL;
    minix_dentry_t *entry;
    buffer_t *ebuf = NULL;
    int ret = EOF;

    ebuf = minix_find_entry(&dir, name, &next, &entry);
    if (!ebuf)
    {
        ret = -ENOENT;
        goto rollback;
    }

    inode = minix_iget(dir->super, entry->nr);

    minix_inode_t *iminode = (minix_inode_t *)inode->desc;
    if (!ISDIR(iminode->mode))
    {
        ret = -ENOTDIR;
        goto rollback;
    }

    task_t *task = running_task();

    if (iminode->mode & ISVTX && task->uid != iminode->uid)
    {
        ret = -EPERM;
        goto rollback;
    }

    if (dir->super != inode->super)
    {
        ret = -ENOSYS;
        goto rollback;
    }

    if (inode->count > 1)
    {
        ret = -EBUSY;
        goto rollback;
    }

    if (!dir_empty(inode))
    {
        ret = -ENOTEMPTY;
        goto rollback;
    }

    assert(iminode->nlinks == 2);

    minix_truncate(inode);
    minix_ifree(inode->super, inode->nr);

    iminode->nlinks = 0;
    inode->buf->dirty = true;;
    inode->nr = 0;

    minix_inode_t *dminode = (minix_inode_t *)dir->desc;

    dminode->nlinks--;
    dir->buf->dirty = true;;;

    entry->nr = 0;
    ebuf->dirty = true;;;

    ret = EOK;

rollback:
    brelse(ebuf);
    iput(inode);
    return ret;
}

int minix_link(inode_t *odir, char *oldname, inode_t *ndir, char *newname)
{
    int ret = EOK;
    buffer_t *buf = NULL;
    char *next = NULL;
    inode_t *inode = NULL;

    if (minix_namei(odir, oldname, &next, &inode) == EOF)
        goto rollback;

    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    // 不支持链接目录
    if (ISDIR(minode->mode))
        goto rollback;

    // 不支持跨分区 link
    if (ndir->super != inode->super)
        goto rollback;

    minix_dentry_t *entry;
    buf = minix_find_entry(&ndir, newname, &next, &entry);
    if (buf)
        goto rollback;

    buf = minix_add_entry(ndir, newname, &entry);
    if (!buf)
    {
        ret = ERROR;
        goto rollback;
    }

    entry->nr = inode->nr;
    buf->dirty = true;;
    minode->nlinks++;
    inode->buf->dirty = true;;

rollback:
    brelse(buf);
    iput(inode);
    return ret;
}

int minix_unlink(inode_t *dir, char *name)
{
    task_t *task = running_task();
    int ret = EOK;
    buffer_t *ebuf = NULL;
    char *next = NULL;
    inode_t *inode = NULL;

    minix_dentry_t *entry;
    ebuf = minix_find_entry(&dir, name, &next, &entry);
    if (!ebuf)
    {
        ret = -ENOENT;
        goto rollback;
    }

    if (next)
    {
        ret = -EISDIR;
        goto rollback;
    }

    inode = minix_iget(dir->super, entry->nr);

    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    if (ISDIR(minode->mode))
    {
        ret = -EISDIR;
        goto rollback;
    }

    if (dir->super != inode->super)
    {
        ret = -ENOSYS;
        goto rollback;
    }

    if (minode->mode & ISVTX && minode->uid != task->uid)
    {
        ret = -EPERM;
        goto rollback;
    }

    assert(minode->nlinks);

    entry->nr = 0;
    ebuf->dirty = true;;;

    minode->nlinks--;
    inode->buf->dirty = true;;

    if (!minode->nlinks)
    {
        minix_truncate(inode);
        minix_ifree(inode->super, inode->nr);
    }

rollback:
    brelse(ebuf);
    iput(inode);
    return ret;
}

int minix_mknod(inode_t *dir, char *name, int mode, int dev)
{
    int ret = EOK;

    char *next = NULL;
    buffer_t *buf = NULL;
    inode_t *inode = NULL;

    minix_dentry_t *entry;
    buf = minix_find_entry(&dir, name, &next, &entry);
    if (buf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    buf = minix_add_entry(dir, name, &entry);
    if (!buf)
    {
        ret = -ERROR;
        goto rollback;
    }

    entry->nr = minix_ialloc(dir->super);
    buf->dirty = true;;
    inode = minix_new_inode(dir->super, entry->nr);
    if (!inode)
    {
        ret = -ERROR;
        goto rollback;
    }

    minix_inode_t *minode = (minix_inode_t *)inode->desc;

    minode->nlinks++;
    minode->mode = mode;

    minode->zone[0] = dev;
    inode->rdev = dev;

    inode->buf->dirty = true;;

rollback:
    brelse(buf);
    iput(inode);
    return ret;
}

int minix_rename(inode_t *odir, char *oldname, inode_t *ndir, char *newname)
{
    buffer_t *obuf = NULL;
    buffer_t *nbuf = NULL;
    inode_t *inode = NULL;

    int ret;

    char *next;
    dentry_t *entry;
    dentry_t *nentry;

    obuf = minix_find_entry(&odir, oldname, &next, &entry);
    if (!obuf)
    {
        ret = -ENOENT;
        goto rollback;
    }

    inode = minix_iget(odir->super, entry->nr);

    if (!ISDIR(inode->mode) && next)
    {
        iput(inode);
        inode = NULL;
    }

    if (inode == inode->super->iroot && inode != get_root_inode())
    {
        super_t *super = inode->super;
        iput(inode);
        inode = super->imount;
        inode->count++;
    }

    nbuf = minix_find_entry(&ndir, newname, &next, &nentry);
    if (nbuf)
    {
        ret = -EEXIST;
        goto rollback;
    }

    u32 nr = entry->nr;
    entry->nr = 0;
    obuf->dirty = true;

    nbuf = minix_add_entry(ndir, newname, &nentry);

    nentry->nr = nr;

    minix_inode_t *minode = inode->desc;
    minode->mtime = ktime();
    inode->mtime = inode->atime = minode->mtime;
    inode->buf->dirty = true;

rollback:
    brelse(nbuf);
    brelse(obuf);
    iput(inode);
    return ret;
}

int minix_read_super(int dev, super_t *super)
{
    buffer_t *buf = bread(dev, 1, BLOCK_SIZE);
    minix_desc_t *desc = buf->data;

    if (desc->magic != MINIX_MAGIC)
    {
        brelse(buf);
        return -EFSUNK;
    }

    super->buf = buf;
    super->desc = desc;
    super->data = (void *)alloc_kpage(1);

    minix_data_t *data = super->data;

    // 读位图
    int idx = 2;

    for (size_t i = 0; i < desc->imap_blocks; i++)
        data->imaps[i] = bread(dev, idx++, BLOCK_SIZE);

    for (size_t i = 0; i < desc->zmap_blocks; i++)
        data->zmaps[i] = bread(dev, idx++, BLOCK_SIZE);

    super->iroot = minix_iget(super, 1);
    super->iroot->mount = super;

    return EOK;
}

int minix_put_super(super_t *super)
{
    minix_data_t *data = (minix_data_t *)super->data;
    minix_desc_t *desc = (minix_desc_t *)super->desc;

    iput(super->iroot);

    for (size_t i = 0; i < desc->imap_blocks; i++)
        brelse(data->imaps[i]);

    for (size_t i = 0; i < desc->zmap_blocks; i++)
        brelse(data->zmaps[i]);

    brelse(super->buf);
    free_kpage((u32)data, 1);

    return EOK;
}

int minix_mkfs(int dev, int icount)
{
    super_t *super = NULL;
    buffer_t *buf = NULL;
    int ret = EOF;

    int total_block = device_ioctl(dev, DEV_CMD_SECTORS_COUNT, 0) / 2;
    assert(total_block);
    assert(icount < total_block);
    if (!icount)
    {
        icount = total_block / 3;
    }

    super = get_free_super();
    super->op = fs_get_op(FS_TYPE_MINIX);
    super->dev = dev;
    super->count++;
    super->data = (void *)alloc_kpage(1);

    buf = bread(dev, 1, BLOCK_SIZE);
    super->buf = buf;
    buf->dirty = true;;

    // 初始化超级块
    minix_desc_t *desc = (minix_desc_t *)buf->data;
    super->desc = desc;

    int inode_blocks = div_round_up(icount * sizeof(minix_inode_t), BLOCK_SIZE);
    desc->inodes = icount;
    desc->zones = total_block;
    desc->imap_blocks = div_round_up(icount, BLOCK_BITS);

    int zcount = total_block - desc->imap_blocks - inode_blocks - 2;
    desc->zmap_blocks = div_round_up(zcount, BLOCK_BITS);

    desc->data = 2 + desc->imap_blocks + desc->zmap_blocks + inode_blocks;
    desc->log_zone_size = 0;
    desc->max_size = BLOCK_SIZE * TOTAL_BLOCK;
    desc->magic = MINIX_MAGIC;

    minix_data_t *data = super->data;

    int idx = 2;
    for (int i = 0; i < desc->imap_blocks; i++, idx++)
    {
        buf = bread(dev, idx, BLOCK_SIZE);
        assert(buf);
        buf->dirty = true;;
        memset(buf->data, 0, BLOCK_SIZE);
        data->imaps[i] = buf;
    }

    for (int i = 0; i < desc->zmap_blocks; i++, idx++)
    {
        buf = bread(dev, idx, BLOCK_SIZE);
        assert(buf);
        buf->dirty = true;;
        memset(buf->data, 0, BLOCK_SIZE);
        data->zmaps[i] = buf;
    }

    // 初始化位图
    idx = minix_balloc(super);

    idx = minix_ialloc(super);
    idx = minix_ialloc(super);

    // 位图尾部置位 TODO:

    // 创建根目录
    task_t *task = running_task();

    list_init(&super->inode_list);

    inode_t *iroot = minix_new_inode(super, 1);
    super->iroot = iroot;

    minix_inode_t *minode = (minix_inode_t *)iroot->desc;

    minode->mode = (0777 & ~task->umask) | IFDIR;
    minode->size = sizeof(minix_dentry_t) * 2; // 当前目录和父目录两个目录项
    minode->nlinks = 2;                        // 一个是 '.' 一个是 name

    buf = bread(dev, minix_bmap(iroot, 0, true), BLOCK_SIZE);
    buf->dirty = true;;

    minix_dentry_t *entry = (minix_dentry_t *)buf->data;
    memset(entry, 0, BLOCK_SIZE);

    strcpy(entry->name, ".");
    entry->nr = iroot->nr;

    entry++;
    strcpy(entry->name, "..");
    entry->nr = iroot->nr;

    brelse(buf);

    ret = EOK;
rollback:
    put_super(super);
    return ret;
}

static fs_op_t minix_op = {
    .mkfs = minix_mkfs,

    .read_super = minix_read_super,
    .put_super = minix_put_super,

    .open = minix_open,
    .close = minix_close,

    .read = minix_read,
    .write = minix_write,
    .readdir = minix_readdir,

    .lnamei = fs_default_nosys,
    .symlink = fs_default_nosys,
    .readlink = fs_default_nosys,

    .permission = minix_permission,
    .namei = minix_namei,
    .mkdir = minix_mkdir,
    .rmdir = minix_rmdir,
    .link = minix_link,
    .unlink = minix_unlink,
    .mknod = minix_mknod,
    .rename = minix_rename
};

void minix_init()
{
    fs_register_op(FS_TYPE_MINIX, &minix_op);
}